/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.trello.rxlifecycle4.harmony;

import ohos.agp.components.Component;

import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.functions.Function;

import com.trello.rxlifecycle4.LifecycleTransformer;
import com.trello.rxlifecycle4.OutsideLifecycleException;
import com.trello.rxlifecycle4.rxlifecycle_annotation.CheckResult;
import com.trello.rxlifecycle4.rxlifecycle_annotation.NonNull;

import static com.trello.rxlifecycle4.RxLifecycle.bind;
import static com.trello.rxlifecycle4.internal.Preconditions.checkNotNull;

/**
 * This class is about life cycle of Rx Harmony.
 */
public class RxLifecycleHarmony {
    private static final String BIND_TO = "Binding to ";
    private static final String NOT_YET_IMPLEMENTED = " not yet implemented";

    private RxLifecycleHarmony() {
        throw new AssertionError("No instances");
    }

    /**
     * Binds the given source to an Ability lifecycle.
     * <p>
     * This helper automatically determines (based on the lifecycle sequence itself) when the source
     * should stop emitting items. In the case that the lifecycle sequence is in the
     * creation phase (START, ACTIVE, etc) it will choose the equivalent destructive phase (STOP,
     * INACTIVE, etc). If used in the destructive phase, the notifications will cease at the next event;
     * for example, if used in INACTIVE, it will unsubscribe in STOP.
     * <p>
     * Due to the differences between the Ability and Fraction lifecycles, this method should only
     * be used for an Ability lifecycle.
     *
     * @param lifecycle the lifecycle sequence of an Ability
     * @return reusable {@link LifecycleTransformer} that unsubscribes the source during the Ability lifecycle
     */
    @NonNull
    @CheckResult
    public static <T> LifecycleTransformer<T> bindAbility(@NonNull final Observable<AbilityEvent> lifecycle) {
        return bind(lifecycle, ABILITY_LIFECYCLE);
    }

    /**
     * Binds the given source to an AbilitySlice lifecycle.
     * <p>
     * This helper automatically determines (based on the lifecycle sequence itself) when the source
     * should stop emitting items. In the case that the lifecycle sequence is in the
     * creation phase (START, ACTIVE, etc) it will choose the equivalent destructive phase (STOP,
     * INACTIVE, etc). If used in the destructive phase, the notifications will cease at the next event;
     * for example, if used in INACTIVE, it will unsubscribe in STOP.
     *
     * @param lifecycle the lifecycle sequence of an AbilitySlice
     * @return reusable {@link LifecycleTransformer} that unsubscribes the source during the AbilitySlice lifecycle
     */
    @NonNull
    @CheckResult
    public static <T> LifecycleTransformer<T> bindAbilitySlice(@NonNull final Observable<AbilitySliceEvent> lifecycle) {
        return bind(lifecycle, ABILITY_SLICE_LIFECYCLE);
    }

    /**
     * Binds the given source to Fragment lifecycle.
     * <p>
     * This helper automatically determines (based on the lifecycle sequence itself) when the source
     * should stop emitting items. In the case that the lifecycle sequence is in the
     * creation phase (CREATE, START, etc) it will choose the equivalent destructive phase (DESTROY,
     * STOP, etc). If used in the destructive phase, the notifications will cease at the next event;
     * for example, if used in PAUSE, it will unsubscribe in STOP.
     * <p>
     * Due to the differences between the Activity and Fragment lifecycles, this method should only
     * be used for Fragment lifecycle.
     *
     * @param lifecycle the lifecycle sequence of Fragment
     * @return reusable {@link LifecycleTransformer} that unsubscribes the source during the Fragment lifecycle
     */
    @NonNull
    public static <T> LifecycleTransformer<T> bindFraction(@NonNull final Observable<FractionEvent> lifecycle) {
        return bind(lifecycle, FRACTION_LIFECYCLE);
    }

    /**
     * Binds the given source to View lifecycle.
     * <p>
     * Specifically, when the View detaches from the window, the sequence will be completed.
     * <p>
     * Warning: you should make sure to use the returned Transformer on the main thread,
     * since we're binding to View (which only allows binding on the main thread).
     *
     * @param view the view to bind the source sequence to
     * @return reusable {@link LifecycleTransformer} that unsubscribes the source during the View lifecycle
     */
    @NonNull
    public static <T> LifecycleTransformer<T> bindView(@NonNull final Component view) {
        checkNotNull(view, "view == null");

        return bind(Observable.create(new ViewDetachesOnSubscribe(view)));
    }

    // Figures out which corresponding next lifecycle event in which to unsubscribe, for Abilities
    private static final Function<AbilityEvent, AbilityEvent> ABILITY_LIFECYCLE =
            new Function<AbilityEvent, AbilityEvent>() {
                @Override
                public AbilityEvent apply(AbilityEvent lastEvent) throws Exception {
                    switch (lastEvent) {
                        case START:
                            return AbilityEvent.STOP;
                        case ACTIVE:
                            return AbilityEvent.INACTIVE;
                        case FOREGROUND:
                            return AbilityEvent.BACKGROUND;
                        case INACTIVE:
                            return AbilityEvent.STOP;
                        case BACKGROUND:
                            return AbilityEvent.STOP;
                        case STOP:
                            throw new OutsideLifecycleException("Cannot bind to Ability lifecycle when outside of it.");
                        default:
                            throw new UnsupportedOperationException(BIND_TO + lastEvent + NOT_YET_IMPLEMENTED);
                    }
                }
            };

    // Figures out which corresponding next lifecycle event in which to unsubscribe, for AbilitySlices
    private static final Function<AbilitySliceEvent, AbilitySliceEvent> ABILITY_SLICE_LIFECYCLE =
            new Function<AbilitySliceEvent, AbilitySliceEvent>() {
                @Override
                public AbilitySliceEvent apply(AbilitySliceEvent lastEvent) throws Exception {
                    switch (lastEvent) {
                        case START:
                            return AbilitySliceEvent.STOP;
                        case ACTIVE:
                            return AbilitySliceEvent.INACTIVE;
                        case FOREGROUND:
                            return AbilitySliceEvent.BACKGROUND;
                        case INACTIVE:
                            return AbilitySliceEvent.STOP;
                        case BACKGROUND:
                            return AbilitySliceEvent.STOP;
                        case STOP:
                            throw new OutsideLifecycleException("Cannot bind to AbilitySlice lifecycle outside of it");
                        default:
                            throw new UnsupportedOperationException(BIND_TO + lastEvent + NOT_YET_IMPLEMENTED);
                    }
                }
            };

    // Figures out which corresponding next lifecycle event in which to unsubscribe, for Fraction
    private static final Function<FractionEvent, FractionEvent> FRACTION_LIFECYCLE =
            new Function<FractionEvent, FractionEvent>() {
                @Override
                public FractionEvent apply(FractionEvent lastEvent) throws Exception {
                    switch (lastEvent) {
                        case ATTACH:
                            return FractionEvent.DETACH;
                        case START:
                            return FractionEvent.STOP;
                        case FOREGROUND:
                            return FractionEvent.BACKGROUND;
                        case ACTIVE:
                            return FractionEvent.INACTIVE;
                        case INACTIVE:
                            return FractionEvent.STOP;
                        case BACKGROUND:
                            return FractionEvent.FOREGROUND;
                        case STOP:
                            return FractionEvent.DETACH;
                        case DETACH:
                            throw new OutsideLifecycleException("Cannot bind to Fraction lifecycle outside of it.");
                        default:
                            throw new UnsupportedOperationException(BIND_TO + lastEvent + NOT_YET_IMPLEMENTED);
                    }
                }
            };
}